using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using Com.CodeGame.CodeTanks2012.DevKit.CSharpCgdk.Model;

namespace Com.CodeGame.CodeTanks2012.DevKit.CSharpCgdk
{
    public sealed class MyStrategy : IStrategy
    {
        private const double TwoPi = 2 * Math.PI;

        #region IStrategy

        private readonly HashSet<long> _usedTarget = new HashSet<long>();
        private SortedList<long, long> attackMap = new SortedList<long, long>();

        private Tank _self;
        private World _world;
        private Move _move;

        public void Move(Tank self, World world, Move move)
        {
            this._move = move;
            this._world = world;
            _self = self;

            AnalyzeAttack();

            //            if (_world.Tick % 100 == 0)
            //            {
            //
            //            }
            //
            //            //Moving
            SelectMovingTarget(self, world);
            //
            //            target = ControlTankFromFrom.GetTarget();
            MoveToTarget();
            //
            //
            FirstStepsMoving();
            //
            //                        SaveFromAttack();
            //
            SelectFightTarget();
            SelectFireOn();
            //
            FireOnFirstTick();


#if DEBUG
            //            ControlTankFromFrom.SetMove(move);
            log4net.GlobalContext.Properties["tick"] = _world.Tick;
            log4net.GlobalContext.Properties["x"] = _self.X;
            log4net.GlobalContext.Properties["y"] = _self.Y;
            log4net.GlobalContext.Properties["angle"] = _self.Angle;
            log4net.GlobalContext.Properties["vx"] = _self.SpeedX;
            log4net.GlobalContext.Properties["vy"] = _self.SpeedY;
            log4net.GlobalContext.Properties["as"] = _self.AngularSpeed;

            log4net.GlobalContext.Properties["tleft"] = _move.LeftTrackPower;
            log4net.GlobalContext.Properties["tright"] = _move.RightTrackPower;
            log4net.GlobalContext.Properties["rotate"] = _move.TurretTurn;
            log4net.GlobalContext.Properties["fire"] = (int)(_move.FireType ?? FireType.None);
            log.Info("tank");
            logt.Info("tank");
//            System.Threading.Thread.Sleep(20);

#endif
        }

        private void AnalyzeAttack()
        {
            foreach (var shell in _world.Shells)
            {
                var tankTarget = (from t in _world.Tanks
                                  where shell.GetAngleTo(t) < Math.PI / 60
                                  orderby shell.GetDistanceTo(t) descending
                                  select t).FirstOrDefault();
                if (tankTarget != null)
                {
                    attackMap[GetTankIdByPlayerName(shell.PlayerName)] = tankTarget.Id;
                }
            }
        }

        public TankType SelectTank(int tankIndex, int teamSize)
        {
            return TankType.Medium;
        }

        #endregion

        #region Fight

        private long _fightTargetId;

        private Unit _extropolateFightTarget;

        private Tank FightTarget
        {
            get { return (from t in _world.Tanks where t.Id == _fightTargetId select t).FirstOrDefault(); }
            set { _fightTargetId = value.Id; }
        }

        private Unit ExtropolatePosition(Unit u, int tick)
        {
            double x = u.X + u.SpeedX * tick / 2;
            double y = u.Y + u.SpeedY * tick / 2;
            return new Point(x, y);
        }

        private void SelectFightTarget()
        {
            if (IsTimeForChangeFightTarget())
            {
                FightTarget = (from t in _world.Tanks
                               orderby FightSortPreference(t) descending
                               where t.IsTeammate == false && t.HullDurability != 0 && t.CrewHealth != 0
                               select t).FirstOrDefault();

            }
            _extropolateFightTarget = ExtropolatePosition(FightTarget, (int)(FightTarget.GetDistanceTo(_self) / 14));
            _move.TurretTurn = _self.GetTurretAngleTo(_extropolateFightTarget);

        }

        private bool IsTimeForChangeFightTarget()
        {
            return FightTarget == null || FightTarget.HullDurability == 0 || FightTarget.CrewHealth == 0;
        }

        private double FightSortPreference(Tank t)
        {
            if (attackMap.ContainsKey(t.Id) && attackMap[t.Id] == _self.Id)
            {
                return 100500;
            }
            else
            {
                return -(attackMap.Count(s => s.Value == t.Id));
            }
        }

        private void SelectFireOn()
        {
            _move.FireType = FireType.None;

            if (_self.GetTurretAngleTo(_extropolateFightTarget) < Math.PI / 90)
            {
                _move.FireType = FireType.PremiumPreferred;
            }

           
        }

        #endregion

        #region First back

        private void FireOnFirstTick()
        {
            if (_world.Tick == 1)
            {
                _move.FireType = FireType.PremiumPreferred;
            }
        }

        private void FirstStepsMoving()
        {
            if (_world.Tick < 30)
            {
                _move.LeftTrackPower = -1;
                _move.RightTrackPower = 0;

            }
        }

        #endregion

        #region Save

        private void SaveFromAttack()
        {

        }

        #endregion

        #region Move

        private int MAX_AGE_TARGETM = 500;

        private Unit target;

        private int targetAge = 0;

        private double PriorityMovingTarget(Bonus bonus)
        {
            var centerMapPriority = 1 - Math.Cos(Math.PI * (bonus.X - _world.Width / 2) / _world.Width) *
                                    Math.Cos(Math.PI * (bonus.Y - _world.Height / 2) / _world.Height);
            var distance = _self.GetDistanceTo(bonus);
            var angle = _self.GetAngleTo(bonus);

            return distance;// * centerMapPriority - Math.Abs(angle);
        }

        private void MoveToTarget()
        {
            if (target == null)
            {
                return;
            }
            //            var angleToTarget = Math.Atan2(_self.Y - target.Y, target.X - _self.X);
            //            var humanAngle = -_self.Angle;
            //
            //            var angle = NormalizeAngle(angleToTarget - humanAngle) * 180 / Math.PI;
            double angle = _self.GetAngleTo(target) * 180 / Math.PI;
            var absangle = Math.Abs(angle);

            double distance = _self.GetDistanceTo(target);
            double brake = 1;


            if (absangle > 120)
            {
                double revAngle = (180 - absangle) * Math.Sign(angle);
                if (revAngle > 90 || distance < 70)
                    brake = 1.5;
                else if (revAngle > 45)
                    brake = 1;
                else
                    brake = revAngle / 45;
                _move.LeftTrackPower = _move.RightTrackPower = -1;
                if (revAngle > 0)
                    _move.RightTrackPower += brake;
                else
                    _move.LeftTrackPower += brake;
            }
            // передний
            else
            {
                if (absangle > 90 || distance < 70)
                    brake = 2;
                else if (absangle > 45)
                    brake = 1;
                else
                    brake = absangle / 45;
                _move.LeftTrackPower = _move.RightTrackPower = 1;
                if (angle > 0)
                    _move.RightTrackPower -= brake;
                else
                    _move.LeftTrackPower -= brake;
            }
        }

        private void SelectMovingTarget(Tank self, World world)
        {
            var minVal = 60;

            if (target is Bonus && !_world.Bonuses.Any(a => a.Id == target.Id))
            {
                target = null;
            }

            if (IsTimeForChangeMovingTarget(self, minVal))
            {
                target = (from t in world.Bonuses
                          orderby PriorityMovingTarget(t) ascending
                          where !_usedTarget.Contains(t.Id)
                          select t).FirstOrDefault();
                if (target != null)
                {
                    _usedTarget.Add(target.Id);
                    targetAge = _world.Tick;
                }
            }
            if (target == null)
            {
                //target = NearestAngle();
            }
            //            if(_world.Tick % 1000 > 500)
            //            {
            //                target = new Point(800, 400);
            //            }
            //            else
            //            {
            //                target = new Point(200, 400);
            //            }

        }

        private bool IsTimeForChangeMovingTarget(Tank self, int minVal)
        {
            return target == null || ((target).GetDistanceTo(self) <= minVal) || (_world.Tick - targetAge) > MAX_AGE_TARGETM;
        }

        #endregion

        #region Helper

#if DEBUG
        private static log4net.ILog log = log4net.LogManager.GetLogger("move");
        private static log4net.ILog logt = log4net.LogManager.GetLogger("tank");
#endif

        private Unit GetTankById(long tank, World world)
        {
            return (from i in world.Tanks where i.Id == tank select i).First();
        }

        private long GetTankIdByPlayerName(string playerName)
        {
            return (from t in _world.Tanks where t.PlayerName == playerName select t.Id).First();
        }

        public double NormalizeAngle(double angle)
        {
            angle = angle % (TwoPi);

            if (angle > Math.PI)
            {
                return angle - TwoPi;
            }
            else if (angle < -Math.PI)
            {
                return angle + TwoPi;
            }
            else
            {
                return angle;
            }
        }

        private Unit NearestAngle()
        {
            double x = _self.X < _world.Width / 2 ? 0 : _world.Width;
            double y = _self.Y < _world.Height / 2 ? 0 : _world.Height;
            return new Point(x, y);
        }

        #endregion
    }

    internal class Point : Unit
    {
        public Point(double x, double y)
            : base(0, 0, 0, x, y, 0, 0, 0, 0)
        {

        }

    }
}

